package com.dkts.oil.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.dkts.oil.config.OilConfig;
import com.dkts.oil.converter.BoundingBoxUtil;
import com.dkts.oil.converter.GeometryConverter;
import com.dkts.oil.converter.UtilsGeoGrid;
import com.dkts.oil.converter.WriterToFile;
import com.dkts.oil.mysql.entity.OilObstacle;
import com.dkts.oil.strtree.GeoJsonSTRtreeIndex;
import com.dkts.oil.util.GeoGridUtil;
import com.dkts.oil.web.vo.request.SiteSelectionRequest;
import com.dkts.oil.web.vo.request.subReq.IndexGeoMountain;
import com.dkts.oil.web.vo.response.AddressPlanResponse;
import com.mysql.cj.xdevapi.DbDoc;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AddressPlanV2ServiceImpl {

    @Autowired
    private OilConfig oilConfig;


    private int getInArePoint(int size, int planIndex) {

        if (planIndex == 0 && size > 0) {
            return 0;
        }
        if (planIndex == 1 && size > 2) {
            return 1;
        }
        if (planIndex == 2 && size > 3) {
            return 2;
        }
        return 0;
    }

    public List<IndexGeoMountain> getMountainIndex(
            Geometry planGeometry, List<Geometry> areaNotAirport, Point point
    ) {
        //储备库优先在山里
        List<OilObstacle> mountains = GeoJsonSTRtreeIndex.queryByPolygon(planGeometry, GeoJsonSTRtreeIndex.mountainTreeBuild);

        List<IndexGeoMountain> indexGeoMountains = new ArrayList<>();
        for (int i = 0; i < areaNotAirport.size(); i++) {
            Geometry geometryTemp = areaNotAirport.get(i);
            for (OilObstacle mountain : mountains) {
                if (mountain.getGeometry() != null && mountain.getGeometry().intersects(geometryTemp)) {
                    indexGeoMountains.add(new IndexGeoMountain(i, geometryTemp, geometryTemp.distance(point) * 100000));
                }
            }
        }
        indexGeoMountains.sort(Comparator.comparingDouble(IndexGeoMountain::getDistance));
        return indexGeoMountains;
    }

    public Geometry getTrainGeo(Geometry planGeometry, List<Geometry> wcgyGeometries){
        IndexGeoMountain getTrainIndex1 = getTrainIndexZyz(wcgyGeometries,planGeometry);
        Geometry candidate1 = null;
        if (getTrainIndex1 == null) {
            candidate1 = wcgyGeometries.remove(0);
        }else {
            candidate1 = wcgyGeometries.remove(getTrainIndex1.getIndex());
        }

        return candidate1;
    }
    /**
     * 主方法，基于格子生成多个方案
     */
    public List<AddressPlanResponse> oilStorageV2(
            SiteSelectionRequest request,
            Geometry planGeometry,
            List<Geometry> obstacles,
            List<Geometry> wcgyzxList
    ) throws Exception {
        List<AddressPlanResponse> resultPlans = new ArrayList<>();
        int planCount = 3;

        // 计算最大面积，决定网格大小（取最大库面积）
        List<Double> areas = Arrays.asList(
                request.getAreaConfig().getTransferStationArea(),
                request.getAreaConfig().getReserveDepotArea(),
                request.getAreaConfig().getConsumptionDepotArea()
        );
        double maxArea = Collections.max(areas);
        double gridSize = Math.sqrt(maxArea);

        List<Geometry> areaIntAirport = UtilsGeoGrid.gridGeometryInfo(
                request.getDefaultPolygon(),
                BoundingBoxUtil.getBoundingBox(request.getDefaultPolygon()),
                true,
                Math.sqrt(request.getAreaConfig().getFuelSupplyCenterArea()),
                wcgyzxList,
                oilConfig.getTempDir(), 3000,4
        );

        List<Pair<Geometry, Double>> sortedWithDistance = areaIntAirport.stream()
                .map(g -> Pair.of(g, g.distance(request.point)))
                .sorted(Comparator.comparingDouble(Pair::getRight))
                .collect(Collectors.toList());

        // 生成规划区域内格子
        List<Geometry> areaNotAirport = UtilsGeoGrid.gridGeometryInfo(
                planGeometry,
                BoundingBoxUtil.getBoundingBox(planGeometry),
                true,
                gridSize,
                obstacles,
                oilConfig.getTempDir(), 3000,4
        );
        List<OilObstacle> mountains = GeoJsonSTRtreeIndex.queryByPolygon(planGeometry, GeoJsonSTRtreeIndex.mountainTreeBuild);



        if (areaNotAirport == null || areaNotAirport.isEmpty()) {
            throw new Exception("规划区域无可用格子");
        }
        if (areaNotAirport.size() < 4) {
            throw new Exception("规划区域过小，只能规划：" + areaNotAirport.size() + "个油库");
        }


        // 按面积从大到小放置库，避免冲突
        List<SiteConfig> siteConfigs = Arrays.asList(
                new SiteConfig("转运站", request.getAreaConfig().getTransferStationArea()),
                new SiteConfig("储备库", request.getAreaConfig().getReserveDepotArea()),
                new SiteConfig("消耗库", request.getAreaConfig().getConsumptionDepotArea()),
                new SiteConfig("外场供油中心", request.getAreaConfig().getFuelSupplyCenterArea())
        );
        siteConfigs.sort(Comparator.comparingDouble(SiteConfig::getArea).reversed());


        for (int planIndex = 0; planIndex < planCount; planIndex++) {
            AddressPlanResponse planResponse = new AddressPlanResponse();
            AddressPlanResponse.PlanItem planItem = new AddressPlanResponse.PlanItem();

            List<Geometry> placedSites = new ArrayList<>();
            Map<String, AddressPlanResponse.Site> siteMap = new HashMap<>();

            // 可用格子
            List<Geometry> availablePoints = new ArrayList<>(areaNotAirport);
            Collections.shuffle(availablePoints);

            for (SiteConfig siteConfig : siteConfigs) {
                Geometry candidate =null;

                //外场供油中心 应该在机场内部。如果内部没有就取离机场中心点最近的点
                if ("外场供油中心".equals(siteConfig.getName())) {
                    if (!areaIntAirport.isEmpty()) {
                        Pair<Geometry, Double> geometryDoublePair = sortedWithDistance.get(getInArePoint(sortedWithDistance.size(), planIndex));
                        candidate = geometryDoublePair.getLeft();
                        Double distance = geometryDoublePair.getRight();
                        log.info("外场供油中心距离加油站的距离：{}", distance * 100000); // 转成米
                    } else {
                        throw new RuntimeException("机场内部无可以区域放置外场供油中心");
//                        if (areaNotAirport.isEmpty()) {
//                            throw new RuntimeException("规划区域无可用点放置外场供油中心");
//                        }
//                        Coordinate airportCenterCoord = new Coordinate(request.getCenterPoint().getLon(), request.getCenterPoint().getLat());
//                        candidate = areaNotAirport.stream()
//                                .min(Comparator.comparingDouble(g -> g.distance(
//                                        GeometryConverter.locationtechGeometryFactory.createPoint(airportCenterCoord))))
//                                .orElseThrow(() -> new RuntimeException("机场外部点计算异常"));
//                        areaNotAirport.remove(candidate);
                    }

                //转运站应该是火车站旁边。转运站应该在火车站附近
                }else if ("转运站".equals(siteConfig.getName())){
                    if (areaNotAirport.isEmpty()) {
                        throw new RuntimeException("规划区域无可用点放置转运站");
                    }
                    IndexGeoMountain getTrainIndex = getTrainIndexZyz(availablePoints,planGeometry);
                    if (getTrainIndex == null) {
                        candidate = availablePoints.remove(0);
                    }else {
                        candidate = availablePoints.remove(getTrainIndex.getIndex());
                    }

                //储备库应该在山里
                }else if ("储备库".equals(siteConfig.getName())) {
                    if (areaNotAirport.isEmpty()) {
                        throw new RuntimeException("规划区域无可用点放置储备库");
                    }
                    List<IndexGeoMountain> getMountainIndex = getMountainIndex(planGeometry, availablePoints, request.point);
                    if (getMountainIndex == null || getMountainIndex.size() == 0) {
                        candidate = availablePoints.remove(0);
                    } else {
                        int index = getInArePoint(getMountainIndex.size(), planIndex);
                        IndexGeoMountain indexGeoMountain = getMountainIndex.get(index);
                        candidate = availablePoints.remove(indexGeoMountain.getIndex());
                    }

                }

                //剩下消耗库
                else {
                    if (availablePoints.isEmpty()) {
                        throw new RuntimeException("规划区域无可用点放置消耗库");
                    }

                    for (Iterator<Geometry> iter = availablePoints.iterator(); iter.hasNext(); ) {
                        Geometry g = iter.next();
                        if (!isInMountain(g, mountains)) {
                            candidate = g;
                            iter.remove(); // 移除这个用掉的点
                            break;
                        }
                    }

                    if (candidate == null) {
//                        throw new RuntimeException("没有足够的非山地区域可用来放置：" + siteConfig.getName());
                        candidate= availablePoints.remove(0);
                    }

                    // 继续用 candidate 做后续 pickSiteFromGrid
                }

                AddressPlanResponse.Site site = pickSiteFromGrid(
                        placedSites,
                        siteConfig.getArea(),
                        siteConfig.getName(),
                        request.getCenterPoint(),
                        candidate,
                        planIndex + 1, request.getPoint()
                );
                siteMap.put(siteConfig.getName(), site);
            }

            // 固定顺序
            planItem.setZyz(siteMap.get("转运站"));
            planItem.setCbk(siteMap.get("储备库"));
            planItem.setXhk(siteMap.get("消耗库"));
            planItem.setWcgyzx(siteMap.get("外场供油中心"));

            planResponse.setPlans(planItem);
            resultPlans.add(planResponse);
        }

        return resultPlans;
    }

    private boolean isInMountain(Geometry point, List<OilObstacle> mountains) {
        for (OilObstacle mountain : mountains) {
            if (mountain.getGeometry() != null && mountain.getGeometry().intersects(point)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 从可用点中选择一个位置放置站点（按面积和不重叠判断）
     */
    public AddressPlanResponse.Site pickSiteFromGrid(
            List<Geometry> placedSites,
            double areaSize,
            String siteName,
            SiteSelectionRequest.AddressPoint airportCenter,
            Geometry candidate,
            int siteIndex,
            Point airportPoint
    ) {
        // 计算库区 buffer 半径（单位与候选点坐标一致）
        double bufferRadius = Math.sqrt(areaSize / Math.PI);

        // 以 candidate 中心点做缓冲区
        Point candidateCenter = candidate.getInteriorPoint();
        Geometry buffer = GeometryConverter.getBuff(candidateCenter, bufferRadius).getEnvelope();

        // 检查 buffer 是否完全落在 candidate 范围内
        if (!buffer.intersects(candidate)) {
            throw new RuntimeException("点超出格子范围");
        }
        double distance = candidateCenter.distance(airportPoint) * 100000; // 转成米
        double bearing = OilAddressV2Helper.calculateBearing(airportCenter, candidateCenter);
        String direction = OilAddressV2Helper.getDirectionName(bearing);

        // 构造方案对象
        AddressPlanResponse.Site site = new AddressPlanResponse.Site();
        site.setName(siteName);
        site.setCenterPoint(new SiteSelectionRequest.AddressPoint() {{
            setLon(candidateCenter.getX());
            setLat(candidateCenter.getY());
        }});
        site.setDistance(distance);
        site.setBearing((int) bearing);
        site.setDirection(direction);
        site.setBound(JSONObject.parseObject(GeometryConverter.geometryToGeoJson(buffer)));
        site.setGeometry(buffer);

        // 写入 geojson 文件
        WriterToFile.writerFileGeoJson(oilConfig.getTempDir() + "siteName-" + siteName + "-" + siteIndex + ".geojson", buffer);

        // 标记已放置
        placedSites.add(buffer);

        return site;
    }

    public IndexGeoMountain getTrainIndexZyz(List<Geometry> availablePoints, Geometry planGeometry) {
        List<IndexGeoMountain> getTrainIndex = new ArrayList<>();
        List<OilObstacle> trains = GeoJsonSTRtreeIndex.queryByPolygon(planGeometry, GeoJsonSTRtreeIndex.trainSTreeBuild);

        for (int i = 0; i < availablePoints.size(); i++) {
            Point point = availablePoints.get(i).getInteriorPoint();
            double minDistance = Double.MAX_VALUE;

            // 查找与当前点距离最近的油障碍物
            for (OilObstacle train : trains) {
                double distance = point.distance(train.getGeometry().getInteriorPoint());
                if (distance < minDistance) {
                    minDistance = distance;
                }
            }

            // 记录每个点的最小距离
            getTrainIndex.add(new IndexGeoMountain(i, availablePoints.get(i), minDistance));
        }

        // 按距离升序排序，获取距离最小的那个点
        getTrainIndex.sort(Comparator.comparingDouble(IndexGeoMountain::getDistance));

        // 返回距离最近的点
        return getTrainIndex.size() > 0 ? getTrainIndex.get(0) : null;
    }


    /**
     * 库类型与面积的简单封装
     */
    private static class SiteConfig {
        private final String name;
        private final double area;

        public SiteConfig(String name, double area) {
            this.name = name;
            this.area = area;
        }

        public String getName() {
            return name;
        }

        public double getArea() {
            return area;
        }
    }

}
